package top.healthylife.gateway.core.config;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronUtil;
import cn.hutool.cron.task.Task;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import top.healthylife.basics.utils.exception.BusinessException;
import top.healthylife.basics.utils.utils.RedisUtil;
import top.healthylife.basics.utils.utils.SpringContextUtil;
import top.healthylife.basics.utils.utils.SystemThreadLocal;
import top.healthylife.gateway.core.service.ParentFace;
import top.healthylife.gateway.dto.annotation.ApiServiceExplain;
import top.healthylife.gateway.dto.annotation.SystemInfo;
import top.healthylife.gateway.dto.request.req.ApiReqDto;
import top.healthylife.gateway.dto.request.result.ApiResultDto;
import top.heathylife.gateway.common.cons.SystemCons;
import top.heathylife.gateway.common.po.ApiParameterPo;
import top.heathylife.gateway.common.po.ParentApiPo;
import top.heathylife.gateway.common.po.SystemPo;
import top.heathylife.gateway.common.utils.SecretJsonUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author maoxiaomeng
 * @describe
 * @datetime 2019/9/19 19:11
 */
@Slf4j
@Component
@SuppressWarnings(value = "all")
public class SystemInit implements ApplicationRunner {
    /**
     * 需要扫描的包
     */
    static final String PACKAGE_NAME = "top.healthylife.gateway";

    /**
     * 同步时间,避免失效
     */
    @Value("${heart.beat.cron:*/1 * * * *}")
    String heartBeatCronTab;
    /**
     * 默认90秒失效
     */
    @Value("${heart.beat.timeOut: 90}")
    Long timeOut;

    /**
     * 系统配置的基础类
     */
    static Class systemClass;
    /**
     * 系统初始化信息
     */
    static SystemPo systemPo;
    /**
     * API与bean初始化结构
     */
    static Map<String, Class> apiClassMap;
    /**
     * 封装的API与请求参数的class对象
     */
    static Map<String, Class> apiParameterClassMap = new HashMap<>();
    /**
     * 接口APIMAP对象
     */
    static Map<String, ParentApiPo> parentApiPoMap = new HashMap<>();
    /**
     * 数据返回结构
     */
    static Map<String, List<ApiParameterPo>> apiReturnParameters = new HashMap<>();

    public static List<ApiParameterPo> getApiReturnParameters(String apiName) {
        return apiReturnParameters.get(apiName);
    }

    public static Map<String, ParentApiPo> getParentApiPoMap() {
        return parentApiPoMap;
    }

    public static Map<String, List<ApiParameterPo>> getApiReturnParameters() {
        return apiReturnParameters;
    }

    public static Map<String, Class> getApiParameterClassMap() {
        return apiParameterClassMap;
    }


    public static Class getSystemClass() {
        return systemClass;
    }

    public static SystemPo getSystemPo() {
        return systemPo;
    }

    public static Map<String, Class> getApiClassMap() {
        return apiClassMap;
    }

    @SneakyThrows
    public static ParentFace getParentFace(String apiName) {
        //检查是否存在这个API的service
        Class aClass = SystemInit.getApiClassMap().get(apiName);
        aClass = Optional.ofNullable(aClass).orElseThrow(() -> new BusinessException("接口[{}]服务不存在", apiName));
        ParentFace parentFace = (ParentFace) SpringContextUtil.getBean(aClass);
        return parentFace;
    }

    /**
     * 根据接口名，直接获取请求入参类型
     *
     * @param apiName 接口名
     * @author maoxiaomeng
     * @date: 2020/7/14 10:05
     * @return: java.lang.Class<? extends com.hare.service.dto.req.ApiReqDto>
     */
    public static Class<? extends ApiReqDto> getReqDto(String apiName) {
        Class aClass = apiClassMap.get(apiName);
        if (aClass == null) {
            throw new BusinessException("没有找到该接口[{}]对应的入参对象", apiName);
        }
        Type[] actualTypeArguments = ((ParameterizedTypeImpl) aClass.getGenericInterfaces()[0]).getActualTypeArguments();
        return (Class<? extends ApiReqDto>) actualTypeArguments[0];
    }

    /**
     * 获取某个接口的返回类型
     *
     * @param apiName 接口名称
     * @author maoxiaomeng
     * @date: 2020/7/15 15:58
     * @return: java.lang.Class<? extends com.hare.service.dto.result.ApiResultDto>
     */
    public static Class<? extends ApiResultDto> getResultDto(String apiName) {
        Class aClass = apiClassMap.get(apiName);
        Type[] actualTypeArguments = ((ParameterizedTypeImpl) aClass.getGenericInterfaces()[0]).getActualTypeArguments();
        Class<? extends ApiResultDto> actualTypeArgument = (Class<? extends ApiResultDto>) actualTypeArguments[1];
        return actualTypeArgument;
    }

    /**
     * 初始化基础配置的信息
     *
     * @param account     凭证的数据，根据请求的参数，把所有的系统参数置换数据
     * @param systemClass
     * @author maoxiaomeng
     * @date: 2020/3/20 19:14
     * @return: void
     */
    public static void setSystemConfig(String account, Class systemClass) {

        account = SecretJsonUtils.decodeBase64(account);
        Object object = JSONObject.parseObject(account, systemClass);
        SystemThreadLocal.set(SystemCons.systemConfig, object);
    }


    public void initSystem() {
        try {
            initConfig();
        } catch (Exception e) {
            log.error("系统初始化错误,启动失败", e);
            System.exit(-1);
        }
    }

    public static void destroyBeforeClose() {
        try {
            List<SystemPo> list = RedisUtil.list(SystemCons.platformKey(), SystemPo.class);
            List<SystemPo> collect = list.stream().filter(e -> !(e.getShortName().equals(systemPo.getShortName()))).collect(Collectors.toList());
            log.info("1.卸载自己模块的平台信息");
            RedisUtil.set(SystemCons.platformKey(), list);
            log.info("2.卸载自己模块的接口信息");
            RedisUtil.delKey(SystemCons.systemHealthKey(systemPo.getApplicationName()));
        } catch (Exception e) {
            log.error("系统清除上下文异常", e);
        }
    }

    private void initConfig() {
        //扫描包获取所有的API接口
        List<Class<?>> faces = ClassUtil.scanPackageBySuper(PACKAGE_NAME, ParentFace.class)
                .stream().sorted(Comparator.comparing(Class::toString)).collect(Collectors.toList());
        List<Class<?>> classes = new ArrayList<>(ClassUtil.scanPackageByAnnotation(PACKAGE_NAME, SystemInfo.class));
        if (classes.size() > 1) {
            log.error("系统配置重复:{}", JSONObject.toJSONString(classes));
            throw new BusinessException("系统配置重复");
        }
        if (classes.size() == 0) {
            System.exit(-1);
        }
        systemClass = Optional.of(classes).map(e -> e.get(0)).orElseThrow(() -> new BusinessException("业务系统名称没有添加注解，请配置SystemConfig，并加上SystemInfo注解"));
        systemPo = Optional.ofNullable(systemClass).map(e -> (SystemInfo) e.getAnnotation(SystemInfo.class))
                .map(e -> new SystemPo()
                        //拿到应用名称
                        .setApplicationName(SpringContextUtil.getApplicationContextName())
                        .setShortName(e.shortName())
                        .setStartTime(LocalDateTime.now())
                        .setSystemName(e.systemName())
                        .setPeriodValidity(e.periodValidity().getPeriodValidity())
                        .setPeriodValidityDesc(e.periodValidity().getDesc())
                        .setDesc(e.desc())
                        .setUrl(e.url())
                ).orElseThrow(() -> new BusinessException("业务系统名称没有添加注解，请配置SystemConfig，并加上SystemInfo注解"));
        //获取所有的接口API地址
        List<ParentApiPo> apis = getApis(faces);
        //保存数据对象
        systemPo.setInterfaceCount(apis.size());
        storeSystemPoInfo(systemPo);
        //获取接口基础信息
        systemPo.setApis(apis);
        parentApiPoMap = apis.stream().collect(Collectors.toMap(ParentApiPo::getApiName, Function.identity()));
        String redisKey = SystemCons.systemHealthKey(systemPo.getShortName());
        //这个地方直接使用最新的,可以刷新最新缓存文件
        SpringContextUtil.getBean(StringRedisTemplate.class).opsForValue().set(redisKey, JSONObject.toJSONString(systemPo), timeOut, TimeUnit.MINUTES);
        task(heartBeatCronTab, timeOut);
    }

    /**
     * 保存服务商信息
     *
     * @param systemPo
     */
    private void storeSystemPoInfo(SystemPo systemPo) {
        String key = SystemCons.platformKey();
        if (RedisUtil.exists(key)) {
            List<SystemPo> list = RedisUtil.list(key, SystemPo.class);
            List<SystemPo> collect = list.stream().filter(e -> !e.getShortName().equals(systemPo.getShortName()))
                    .collect(Collectors.toList());
            collect.add(systemPo);
            RedisUtil.set(key, collect);
            return;
        }
        RedisUtil.set(key, ListUtil.of(systemPo));
    }

    @SneakyThrows
    private List<ParentApiPo> getApis(List<Class<?>> faces) {
        apiClassMap = new HashMap<>(faces.size());
        List<ParentApiPo> parentApiPoList = new LinkedList<>();
        for (Class<?> face : faces) {
            if (face.isAnnotationPresent(ApiServiceExplain.class)) {
                ApiServiceExplain annotation = face.getAnnotation(ApiServiceExplain.class);
                //获取接口名
                String apiName = annotation.apiName();
                //获取价格
                long price = annotation.price();
                //接口文档地址
                String docUrl = annotation.docUrl();
                //失效时间
                long expiresTime = annotation.expiresTime();
                //接口解释
                String info = annotation.info();
                apiClassMap.put(apiName, face);
                //添加参数集
                Type[] genericList = getGenericList(face);
                Class reqClass = (Class) genericList[0];
                Class resClass = (Class) genericList[1];
                if (apiParameterClassMap.containsKey(apiName)) {
                    throw new BusinessException("API名称" + apiName + "重复");
                }
                apiParameterClassMap.put(apiName, reqClass);
                List<ApiParameterPo> returnParameters = getFields(getResultDto(apiName)).getList();
                apiReturnParameters.put(apiName, returnParameters);

                //获取真实的接口
                List<ApiParameterPo> parameters = getParameter(reqClass);

                parentApiPoList.add(new ParentApiPo()
                        .setStatus(true)
                        .setInfo(info)
                        .setPrice(price)
                        .setApiName(apiName)
                        .setCls(reqClass.getName())
                        .setResCls(resClass.getName())
                        .setDocUrl(docUrl)
                        .setParameters(parameters)
                        .setSystemName(systemPo.getSystemName())
                        .setShortName(systemPo.getShortName())
                        .setExpiresTime(expiresTime));
            } else {
                log.error("服务,{},未添加接口说明注解ApiServiceExplain", face);
                throw new RuntimeException("未添加接口说明注解ApiServiceExplain");
            }
        }
        return parentApiPoList;
    }

    @SneakyThrows
    public static ApiParameterPo getFields(Class cls) {
        return getFields(cls, null, null, "$", "");
    }

    /**
     * 根据实体类对象获取所有的参与和参数名称解释,类型
     *
     * @param cls cls对象
     * @return
     */
    @SneakyThrows
    public static ApiParameterPo getFields(Class cls, Field fieldName, String objectName, String absoluteKey, String absoluteTypeKey) {
        if (Objects.isNull(cls)) {
            return null;
        }
        // 特殊处理基类
        if (ClassUtil.isBasicType(cls)) {
            return new ApiParameterPo().setType(cls);
        }
        // 去重处理,防止无限递归(树形结构)
        // 优化去重,旧的去重,是对一个类中约束,不允许重复出现类型相同的class,而实际上,相同的类型,它们只要不在同一条属性链上,就应当可以允许出现
        if (StrUtil.count(absoluteTypeKey, cls.getSimpleName()) > 1) {
            return new ApiParameterPo().setKey(cls.getSimpleName()).setType(cls).setTypeName(cls.getSimpleName());
        }

        // 记录cls父类的泛型
        Class t = null;
        Class originalClass = cls;
        String originalAbsoluteKey = absoluteKey;
        String originalAbsoluteTypeKey = absoluteTypeKey;
        // 获取api注解信息
        ApiModel apiModel = (ApiModel) cls.getAnnotation(ApiModel.class);
        String info = cls.getSimpleName();
        if (StrUtil.isNotBlank(objectName)) {
            info = objectName;
        } else if (Optional.ofNullable(apiModel).isPresent()) {
            String value = apiModel.value();
            info = value;
        }
        // 记录cls的所有成员变量
        List<Field> fieldList = new ArrayList<>();
        // 遍历所有父类,获取成员变量
        while (cls != null) {
            //获取父类以及父类所带的泛型
            Type superClassType = cls.getGenericSuperclass();
            //如果superClassType 携带泛型
            if (superClassType instanceof ParameterizedType) {
                //就把type强转为参数化泛型,并获取实际的泛型参数数组
                Type[] actualTypes = ((ParameterizedType) superClassType).getActualTypeArguments();
                if (ObjectUtil.isNotNull(actualTypes)) {
                    Type actualType = actualTypes[0];
                    if (actualType instanceof Class) {
                        t = originalClass.equals(actualType) ? null : (Class) actualType;
                    }
                }
            }
            fieldList.addAll(Arrays.asList(cls.getDeclaredFields()));
            cls = cls.getSuperclass();
        }
        // 封装子节点数据
        List<ApiParameterPo> paramList = new ArrayList<ApiParameterPo>();
        for (Field field : fieldList) {
            Class type = field.getType();
            // 获取api注解信息
            ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
            if (apiModelProperty == null || apiModelProperty.hidden()) {
                continue;
            }
            String propertyInfo = apiModelProperty.value();
            if (ObjectUtil.isEmpty(propertyInfo)) {
                propertyInfo = apiModelProperty.name();
            }
            String fieldAbsoluteKey = originalAbsoluteKey + "." + field.getName();
            String fieldAbsoluteTypeKey = originalAbsoluteTypeKey + "." + field.getType().getSimpleName();
            ApiParameterPo param = new ApiParameterPo()
                    .setInfo(propertyInfo)
                    .setExample(apiModelProperty.example())
                    .setExtend(apiModelProperty.reference())
                    .setType(type)
                    .setKey(field.getName())
                    .setAbsoluteKey(fieldAbsoluteKey);
            // 处理List
            Class tmp;

            //如果是对象泛型 T obj;
            if (type.equals(Object.class) && apiModelProperty.dataType().equals("T")) {
                Type typeName = ((ParameterizedTypeImpl) originalClass.getGenericSuperclass()).getActualTypeArguments()[0];
                tmp = (Class) typeName;
                //构建多出的层次结构
                ApiParameterPo allFields = getFields(tmp, field, propertyInfo, fieldAbsoluteKey, fieldAbsoluteTypeKey);
                param.setTypeName(tmp.getSimpleName());
                param.setType(tmp);
                paramList.add(allFields);
                continue;
            }

            if (List.class.equals(type)) {
                paramList.add(param);
                //获取该field的泛型的type  List<>
                Type tType = ((ParameterizedTypeImpl) field.getGenericType()).getActualTypeArguments()[0];
                //获取type的名称
                String typeName = tType.getTypeName();
                //处理 List<T> items typeName =  T
                if ("T".equals(typeName)) {
                    //t=xxItems.class
                    if (t != null) {
                        fieldAbsoluteTypeKey = originalAbsoluteTypeKey + "." + typeName;
                        ApiParameterPo allFields = getFields(t, field, propertyInfo, fieldAbsoluteKey, fieldAbsoluteTypeKey);
                        ArrayList<ApiParameterPo> nodeList = new ArrayList<>();
                        nodeList.add(allFields);
                        param.setList(nodeList);
                    }
                    continue;
                }
                //记录最里层的class  List<List<T>>
                //如果该type List<Object.class>
                if (tType instanceof Class) {
                    //记录泛型的class
                    tmp = (Class) tType;
                    fieldAbsoluteTypeKey = originalAbsoluteTypeKey + "." + tmp.getSimpleName();
                } else {
                    //type 有可能还是泛型 ,再取一层  List<List<Object.class>>
                    typeName = ((ParameterizedTypeImpl) tType).getActualTypeArguments()[0].getTypeName();
                    tmp = Class.forName(typeName);
                    fieldAbsoluteTypeKey = originalAbsoluteTypeKey + "." + typeName;
                    //构建多出的层次结构
                    ApiParameterPo innerApiParameterPo = new ApiParameterPo();
                    innerApiParameterPo
                            .setType(List.class).setExample("-")
                            .setTypeName("List").setKey("_child")
                            .setAbsoluteKey(fieldAbsoluteKey + "." + field.getName());
                    List<ApiParameterPo> childList = new ArrayList<>();
                    childList.add(innerApiParameterPo);
                    param.setList(childList);
                    param = innerApiParameterPo;
                }
                Class childClass = tmp;

                List<ApiParameterPo> childList = new ArrayList<>();
                if (ClassUtil.isBasicType(childClass) || "java.lang.String".equals(childClass.getName())) {
                    ApiParameterPo apiParameterPo = new ApiParameterPo()
                            .setType(childClass)
                            .setAbsoluteKey(fieldAbsoluteKey)
                            .setKey("-")
                            .setInfo(propertyInfo);
                    childList.add(apiParameterPo);
                    param.setList(childList);
                    continue;
                }
                childList.add(getFields(childClass, field, propertyInfo, fieldAbsoluteKey, fieldAbsoluteTypeKey));
                param.setList(childList);
                continue;
            }


            // 处理java类
            if (type.getName().startsWith("java") || type.isPrimitive()) {
                paramList.add(param);
                continue;
            }
            // 递归处理
            // 为了解决类复用的问题,同一个类复用到不同的地方,类名称不一样,正常来说应该一致旧的写法是在递归到类内部获取类上的注解含义
            // 新的写法是在获取字段的时候,如果该field是对象,则获取对象含义,递归时传入,参看 百融-借贷意向验证的出参
            ApiParameterPo fields = getFields(type, field, propertyInfo, fieldAbsoluteKey, fieldAbsoluteTypeKey);
            paramList.add(fields);
        }
        if (paramList.isEmpty()) {
            paramList = null;
        }
        String fieldNameStr = fieldName == null ? originalClass.getSimpleName() : fieldName.getName();
        return new ApiParameterPo()
                .setInfo(info)
                .setType(originalClass)
                .setKey(fieldNameStr)
                .setList(paramList)
                .setAbsoluteKey(originalAbsoluteKey);
    }

    private static List<Field> getAllFields(Class cls, Class t, Class originalClass, List<Field> fieldList) {
        while (cls != null) {
            //获取父类以及父类所带的泛型
            Type superClassType = cls.getGenericSuperclass();
            //如果superClassType 携带泛型
            if (superClassType instanceof ParameterizedType) {
                //就把type强转为参数化泛型,并获取实际的泛型参数数组
                Type[] actualTypes = ((ParameterizedType) superClassType).getActualTypeArguments();
                if (ObjectUtil.isNotNull(actualTypes)) {
                    Type actualType = actualTypes[0];
                    if (actualType instanceof Class) {
                        t = originalClass.equals(actualType) ? null : (Class) actualType;
                    }
                }
            }
            fieldList.addAll(Arrays.asList(cls.getDeclaredFields()));
            cls = cls.getSuperclass();
        }
        return fieldList;
    }

    protected static Type[] getGenericList(Class<?> cls) {
        Type obj = cls.getGenericInterfaces()[0];
        ParameterizedType parameterizedType = (ParameterizedType) obj;
        return parameterizedType.getActualTypeArguments();
    }

    protected static Class getResParameterClass(Class<?> cls) {
        Type obj = cls.getGenericInterfaces()[0];
        ParameterizedType parameterizedType = (ParameterizedType) obj;
        Class typeArgument = (Class) parameterizedType.getActualTypeArguments()[1];
        return typeArgument;
    }


    /**
     * 根据类型直接获取参数解释名称
     *
     * @param cls 实体的类class
     * @author maoxiaomeng
     * @date: 2020/3/20 16:00
     * @return: java.util.List<com.hare.service.function.callapi.po.ApiParameterPo>
     */
    public static List<ApiParameterPo> getParameter(Class<?> cls) {
        List<Field> fieldList = new ArrayList<>();
        while (cls != null) {
            //获取父类以及父类所带的泛型
            Type superClassType = cls.getGenericSuperclass();
            //如果superClassType 携带泛型
            fieldList.addAll(Arrays.asList(cls.getDeclaredFields()));
            cls = cls.getSuperclass();
        }
        Class currentClass = cls;
        return fieldList.stream().filter(e -> !"serialVersionUID".equals(e.getName())).map(e -> {
            if (e.isAnnotationPresent(ApiModelProperty.class)) {
                ApiModelProperty swagger = e.getAnnotation(ApiModelProperty.class);
                if (!swagger.hidden()) {
                    return new ApiParameterPo()
                            .setInfo(swagger.value())
                            .setType(e.getType())
                            .setNecessary(swagger.required())
                            .setKey(e.getName())
                            .setExample(swagger.example());
                }
                return null;
            } else {
                log.error("属性名未添加ApiModelProperty注解Class:{},name:{}", currentClass, e.getName());
                throw new BusinessException("类:{} 属性名:{} 未添加ApiModelProperty", currentClass.getSimpleName(), e.getName());
            }
        }).filter(ObjectUtil::isNotEmpty).collect(Collectors.toList());
    }

    /**
     * 定时同步reids数据库
     */
    /**
     * 时时更新API缓存中的对象
     *
     * @param cronTab  同步的值
     * @param redisKey 字段说明
     * @param timeout  超时时间
     * @author maoxiaomeng
     * @date: 2020/3/20 14:36
     * @return: void
     */
    public static void task(String cronTab, long timeout) {
        String apiInfoKey = SystemCons.systemHealthKey(systemPo.getShortName());
        String key = SystemCons.platformKey();
        CronUtil.schedule(cronTab, (Task) () -> {
            if (RedisUtil.exists(apiInfoKey)) {
                //更新Redis缓存的有效时间,默认每分钟同步一次,默认90秒失效
                RedisUtil.expire(apiInfoKey, timeout, TimeUnit.SECONDS);
            } else {
                RedisUtil.set(apiInfoKey, systemPo, timeout, TimeUnit.SECONDS);
            }
            log.debug("接口信息同步成功,服务名{}", apiInfoKey);
            if (RedisUtil.exists(key)) {
                //查找存活的keyList
                String prefix = "interface:api:gateway:";
                String[] strings = RedisUtil.matchKeyRight(prefix);
                List<String> availableServiceName =
                        Arrays.stream(strings).map(e -> e.replace(prefix, "")).collect(Collectors.toList());
                Predicate<SystemPo> filter = e -> availableServiceName.contains(e.getShortName());
                List<SystemPo> list = RedisUtil.list(key, SystemPo.class);
                List<SystemPo> collect = list.stream().filter(filter).collect(Collectors.toList());
                RedisUtil.set(key, collect, timeout, TimeUnit.SECONDS);
                log.debug("服务信息同步成功,服务名{}", key);
            } else {
                SystemPo apis = BeanUtil.copyProperties(systemPo, SystemPo.class, "apis");
                RedisUtil.set(key, ListUtil.of(apis), timeout, TimeUnit.SECONDS);
                log.debug("服务信息同步成功,服务名{}", key);
            }
        });
        // 支持秒级别定时任务
        CronUtil.setMatchSecond(true);
        CronUtil.start();
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        initSystem();
    }


}
